

using System;
using System.Collections.Generic;
using System.Text;

using System.Collections;
using System.Data;
using System.Web;
using JTC.MiniCommerce.Core;
using JTC.MiniCommerce.Localization;
using JTC.MiniCommerce.Store.Services.TaxService;
using JTC.MiniCommerce.Store.Services.ShippingService;
using JTC.MiniCommerce.Store.Services.PaymentService;
using JTC.MiniCommerce.Store.Services.MessageService;
using SubSonic;


namespace JTC.MiniCommerce.Store {
  public partial class OrderController {

    #region Constants

    private const int ORDER_NUMBER_LENGTH = 16;
    private const string EXCEPTION_TOO_MANY_CARTS = "Cannot have more than one cart.";
    private const string ORDER_REFUNDED = "OrderRefunded";
    private const string ORDER_CHARGED = "OrderCharged";
    private const string SKU = "Sku";
    private const string SYSTEM = "System";
    private const string PAYPAL = "PayPal";
    private const string PAYPAL_STANDARD = "PayPal Standard";
    private const string SUCCESS = "SUCCESS";

    #endregion
    
    #region Methods
   
    #region Public

    /// <summary>
    /// Fetches the order item by sku and attributes.
    /// </summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="sku">The sku.</param>
    /// <param name="attributes">The attributes.</param>
    /// <returns></returns>
    public OrderItemCollection FetchOrderItemBySkuAndAttributes(int orderId, string sku, string attributes) {
      Query query = new Query(OrderItem.Schema).
        AddWhere(OrderItem.Columns.OrderId, orderId).
        AddWhere(OrderItem.Columns.Sku, sku).
        AddWhere(OrderItem.Columns.Attributes, attributes);
      OrderItemCollection orderItemCollection = new OrderItemController().FetchByQuery(query);
      return orderItemCollection;
    }

    /// <summary>
    /// Adds the item to order.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="productId">The product id.</param>
    /// <param name="name">The name.</param>
    /// <param name="sku">The sku.</param>
    /// <param name="quantity">The quantity.</param>
    /// <param name="pricePaid">The price paid.</param>
    /// <param name="weight">The weight.</param>
    /// <param name="attributes">The attributes.</param>
    public void AddItemToOrder(string userName, int productId, string name, string sku, int quantity, decimal pricePaid, decimal weight, string attributes) {
      int orderId = ProvisionOrder(userName);
      OrderItemCollection orderItemCollection = this.FetchOrderItemBySkuAndAttributes(orderId, sku, attributes);
      if (orderItemCollection.Count == 1) {
        orderItemCollection[0].Quantity += quantity;
        orderItemCollection[0].Save(userName);
      }
      else {
        OrderItem orderItem = new OrderItem();
        orderItem.OrderId = orderId;
        orderItem.ProductId = productId;
        orderItem.Name = name;
        orderItem.Sku = sku;
        orderItem.Quantity = quantity;
        orderItem.PricePaid = pricePaid;
        orderItem.Attributes = attributes;
        orderItem.Weight = weight;
        orderItem.Save(userName);
      }
      ResetShippingAndTaxAndDiscount(orderId, userName);
    }

    /// <summary>
    /// Gets the order id.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public int GetOrderId(string userName) {
      int orderId = 0;
      Query query = new Query(Order.Schema).
        AddWhere(Order.Columns.UserName, userName).
        AddWhere(Order.Columns.OrderStatusId, (int)OrderStatus.NotProcessed);
      query.SelectList = Order.Columns.OrderId;
      object result = query.ExecuteScalar();
      if(result != null) {
        int.TryParse(result.ToString(), out orderId);
      }
      return orderId;
    }

    /// <summary>
    /// Fetches the order.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public Order FetchOrder(string userName) {
      Order order = new Order();
      Query query = new Query(Order.Schema).
        AddWhere(Order.Columns.UserName, userName).
        AddWhere(Order.Columns.OrderStatusId, (int)OrderStatus.NotProcessed);
      OrderCollection orderCollection = this.FetchByQuery(query);
      if(orderCollection.Count > 1){
        throw new InvalidOperationException(EXCEPTION_TOO_MANY_CARTS);
      }
      if(orderCollection.Count == 1) {
        order = orderCollection[0];
      }
      return order;
    }
    
    /// <summary>
    /// Fetches the order.
    /// </summary>
    /// <param name="guid">The GUID.</param>
    /// <returns></returns>
    public Order FetchOrder(Guid guid) {
      Order order = new Order();
      Query query = new Query(Order.Schema).AddWhere(Order.Columns.OrderGuid, guid.ToString());
      OrderCollection orderCollection = this.FetchByQuery(query);
      if(orderCollection.Count > 1) {
        throw new InvalidOperationException(EXCEPTION_TOO_MANY_CARTS);
      }
      if(orderCollection.Count == 1) {
        order = orderCollection[0];
      }
      return order;      
    }

    /// <summary>
    /// Fetches the order.
    /// </summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public Order FetchOrder(int orderId, string userName) {
      Order order = new Order();
      Query query = new Query(Order.Schema).
        AddWhere(Order.Columns.OrderId, orderId).
        AddWhere(Order.Columns.UserName, userName).
        AddWhere(Order.Columns.OrderStatusId, Comparison.NotEquals, (int)OrderStatus.NotProcessed);
      OrderCollection orderCollection = this.FetchByQuery(query);
      if(orderCollection.Count > 1) {
        throw new InvalidOperationException(EXCEPTION_TOO_MANY_CARTS);
      }
      if(orderCollection.Count == 1) {
        order = orderCollection[0];
      }
      return order;
    }

    /// <summary>
    /// Gets the item count in order.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public int GetItemCountInOrder(string userName) {
      int orderItemCount = 0;
      Order order = FetchOrder(userName);
      orderItemCount = order.OrderItemCollection.Count;
      return orderItemCount;
    }

    /// <summary>
    /// Adjusts the quantity.
    /// </summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="orderItemId">The order item id.</param>
    /// <param name="quantity">The quantity.</param>
    /// <param name="userName">Name of the user.</param>
    public void AdjustQuantity(int orderId, int orderItemId, int quantity, string userName) {
      Query query = new Query(OrderItem.Schema);
      query.AddWhere(OrderItem.Columns.OrderItemId, orderItemId); 
      query.AddUpdateSetting(OrderItem.Columns.Quantity, quantity);
      query.AddUpdateSetting(OrderItem.Columns.ModifiedOn, DateTime.UtcNow.ToString());
      query.AddUpdateSetting(OrderItem.Columns.ModifiedBy, userName);
      query.Execute();
      ResetShippingAndTaxAndDiscount(orderId, userName);
    }

    /// <summary>
    /// Removes the item.
    /// </summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="orderItemId">The order item id.</param>
    /// <param name="userName">Name of the user.</param>
    public void RemoveItem(int orderId, int orderItemId, string userName) {
      Query query = new Query(OrderItem.Schema);
      query.QueryType = QueryType.Delete;
      query.AddWhere(OrderItem.Columns.OrderItemId, orderItemId);
      query.Execute();
      ResetShippingAndTaxAndDiscount(orderId, userName);
    }
    
    /// <summary>
    /// Fetches the orders for user.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public OrderCollection FetchOrdersForUser(string userName) {
      IDataReader reader = new Query(Order.Schema).
        AddWhere(Order.Columns.UserName, Comparison.Equals, userName).
        AddWhere(Order.Columns.OrderStatusId, Comparison.NotEquals, (int)OrderStatus.NotProcessed).
        ExecuteReader();
      OrderCollection orderCollection = new OrderCollection();
      orderCollection.LoadAndCloseReader(reader);
      return orderCollection;
    }
    
    #endregion

    #region Static Methods

    /// <summary>
    /// Refunds the specified transaction.
    /// </summary>
    /// <param name="transaction">The transaction.</param>
    /// <param name="userName">Name of the user.</param>
    public static void Refund(Transaction transaction, string userName) {
      PaymentService paymentService = new PaymentService();
      Transaction refundTransaction = paymentService.Refund(transaction);
      refundTransaction.Save(userName);
      Order order = refundTransaction.Order;
      order.OrderStatusId = (int)OrderStatus.OrderRefunded;
      order.Save(userName);
      //Add an OrderNote
      OrderNote orderNote = new OrderNote();
      orderNote.OrderId = order.OrderId;
      orderNote.Note = LocalizationUtility.GetStoreString(ORDER_REFUNDED);
      orderNote.Save(userName);
      //send off the notifications
      MessageService messageService = new MessageService();
      messageService.SendOrderRefundToCustomer(order);
    }

    /// <summary>
    /// Authorizes this instance.
    /// </summary>
    /// <returns></returns>
    public static Transaction Authorize() {
      throw new NotImplementedException("Not implemented");
    }

    /// <summary>
    /// Charges the specified order.
    /// </summary>
    /// <param name="order">The order.</param>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public static Transaction Charge(Order order, string userName) {
      Transaction transaction = null;
      try {
        //update the order with IP
        order.IPAddress = HttpContext.Current.Request.UserHostAddress;
        PaymentService paymentService = new PaymentService();
        transaction = paymentService.Charge(order);
        order.OrderStatusId = (int)OrderStatus.ReceivedPaymentProcessingOrder;
        order.Save(userName);
        transaction.Save(userName);
      }
      catch(Exception ex) {
        Logger.Error(typeof(OrderController).Name + ".Charge", ex);
        throw;
      }

      try {
        //Add an OrderNote
        OrderNote orderNote = new OrderNote();
        orderNote.OrderId = order.OrderId;
        orderNote.Note = LocalizationUtility.GetStoreString(ORDER_CHARGED);
        orderNote.Save(userName);
        //Adjust the Inventory
        Sku sku;
        foreach(OrderItem orderItem in order.OrderItemCollection) {
          sku = new Sku(SKU, orderItem.Sku);
          sku.Inventory = sku.Inventory - orderItem.Quantity;
          sku.Save(SYSTEM);
        }
        //Send out the messages
        MessageService messageService = new MessageService();
        messageService.SendOrderReceivedNotificationToCustomer(order);
        messageService.SendOrderReceivedNotificationToMerchant(order);
      }
      catch(Exception ex) {
        //swallow the exception here because the transaction is saved
        //and, while this is an inconvenience, it's not critical
        Logger.Error(typeof(OrderController).Name + ".Charge", ex);
      }
      return transaction;
    }

    /// <summary>
    /// Fetches the shipping options.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public static ShippingOptionCollection FetchShippingOptions(string userName) {
      Order order = new OrderController().FetchOrder(userName);
      ShippingService shippingService = new ShippingService();
      ShippingOptionCollection shippingOptionCollection = shippingService.GetShippingOptions(order);
      return shippingOptionCollection;
    }

    /// <summary>
    /// Calculates the tax.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    public static void CalculateTax(string userName) {
      Order order = new OrderController().FetchOrder(userName);
      TaxService taxService = new TaxService();
      taxService.GetTaxRate(order);
      foreach(OrderItem orderItem in order.OrderItemCollection) {
        orderItem.Save(userName);
      }
      order.TaxAmount = order.TaxTotal;
      order.Save(userName);
    }

    /// <summary>
    /// Sets the shipping.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="shippingAmount">The shipping amount.</param>
    /// <param name="shippingMethod">The shipping method.</param>
    public static void SetShipping(string userName, decimal shippingAmount, string shippingMethod) {
      Order order = new OrderController().FetchOrder(userName);
      order.ShippingAmount = shippingAmount;
      order.ShippingMethod = shippingMethod;
      order.Save(userName);
    }

    /// <summary>
    /// Sets the express checkout.
    /// </summary>
    /// <param name="order">The order.</param>
    /// <param name="returnUrl">The return URL.</param>
    /// <param name="cancelUrl">The cancel URL.</param>
    /// <param name="authorizeOnly">if set to <c>true</c> [authorize only].</param>
    /// <returns></returns>
    public static string SetExpressCheckout(Order order, string returnUrl, string cancelUrl, bool authorizeOnly) {
      string token = string.Empty;
      PaymentService paymentService = new PaymentService();
      token = paymentService.SetExpressCheckout(order, returnUrl, cancelUrl, authorizeOnly);
      return token;
    }

    /// <summary>
    /// Gets the express checkout.
    /// </summary>
    /// <param name="token">The token.</param>
    /// <returns></returns>
    public static PayPalPayer GetExpressCheckout(string token) {
      PaymentService paymentService = new PaymentService();
      PayPalPayer payPalPayer = paymentService.GetExpressCheckoutDetails(token);
      return payPalPayer;
    }

    /// <summary>
    /// Does the express checkout.
    /// </summary>
    /// <param name="order">The order.</param>
    /// <param name="authorizeOnly">if set to <c>true</c> [authorize only].</param>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    public static Transaction DoExpressCheckout(Order order, bool authorizeOnly, string userName) {
      Transaction transaction = null;
      try {
        PaymentService paymentService = new PaymentService();
        transaction = paymentService.DoExpressCheckout(order, authorizeOnly);
        order.OrderStatusId = (int)OrderStatus.ReceivedPaymentProcessingOrder;
        order.Save(userName);
        transaction.Save(userName);
      }
      catch(Exception ex) {
        Logger.Error(typeof(OrderController).Name + ".DoExpressCheckout", ex);
        throw;
      }

      try {
        //Adjust the Inventory
        Sku sku;
        foreach(OrderItem orderItem in order.OrderItemCollection) {
          sku = new Sku(SKU, orderItem.Sku);
          sku.Inventory = sku.Inventory - orderItem.Quantity;
          sku.Save(SYSTEM);
        }
        //Send out the messages
        MessageService messageService = new MessageService();
        messageService.SendOrderReceivedNotificationToCustomer(order);
        messageService.SendOrderReceivedNotificationToMerchant(order);
      }
      catch(Exception ex) {
        //swallow the exception here because the transaction is saved
        //and, while this is an inconvenience, it's not critical
        Logger.Error(typeof(OrderController).Name + ".DoExpressCheckout", ex);
      }
      return transaction;
    }

    /// <summary>
    /// Creates the cart URL.
    /// </summary>
    /// <param name="order">The order.</param>
    /// <param name="returnUrl">The return URL.</param>
    /// <param name="cancelUrl">The cancel URL.</param>
    /// <returns></returns>
    public static string CreateCartUrl(Order order, string returnUrl, string cancelUrl) {
      string url = string.Empty;
      PaymentService paymentService = new PaymentService();
      url = paymentService.CreateCartUrl(order, returnUrl, cancelUrl);
      return url;
    }

    /// <summary>
    /// Synchronizes the specified args.
    /// </summary>
    /// <param name="args">The args.</param>
    /// <returns></returns>
    public static string Synchronize(params object[] args) {
      PaymentService paymentService = new PaymentService();
      string response = paymentService.Synchronize(args);
      return response;
    }

    /// <summary>
    /// Commits the standard transaction.
    /// </summary>
    /// <param name="order">The order.</param>
    /// <param name="transactionId">The transaction id.</param>
    /// <param name="grossAmount">The gross amount.</param>
    /// <returns></returns>
    public static Transaction CommitStandardTransaction(Order order, string transactionId, decimal grossAmount) {
      Transaction transaction = null;
      try {
      order.OrderStatusId = (int)OrderStatus.ReceivedPaymentProcessingOrder;
      order.Save(SYSTEM);
      transaction = new Transaction();
      transaction.OrderId = order.OrderId;
      transaction.TransactionTypeId = (int)TransactionType.Charge;
      transaction.PaymentMethod = PAYPAL;
      transaction.GatewayName = PAYPAL_STANDARD;
      transaction.GatewayResponse = SUCCESS;
      transaction.GatewayTransactionId = transactionId;
      transaction.GrossAmount = grossAmount;
      transaction.TransactionDate = DateTime.UtcNow;
      transaction.Save(SYSTEM);
      }
      catch(Exception ex) {
        Logger.Error(typeof(OrderController).Name + "CommitStandardTransaction", ex);
        throw;
      }

      try {
        //Adjust the Inventory
        Sku sku;
        foreach(OrderItem orderItem in order.OrderItemCollection) {
          sku = new Sku("Sku", orderItem.Sku);
          sku.Inventory = sku.Inventory - orderItem.Quantity;
          sku.Save("System");
        }
        //Send out the messages
        MessageService messageService = new MessageService();
        messageService.SendOrderReceivedNotificationToCustomer(order);
        messageService.SendOrderReceivedNotificationToMerchant(order);
      }
      catch(Exception ex) {
        //swallow the exception here because the transaction is saved
        //and, while this is an inconvenience, it's not critical
        Logger.Error("Charge", ex);
      }
      
      return transaction;      
    }

    /// <summary>
    /// Migrates the cart.
    /// </summary>
    /// <param name="anonymousCartId">The anonymous cart id.</param>
    /// <param name="registeredCartId">The registered cart id.</param>
    public static void MigrateCart(string anonymousCartId, string registeredCartId) {
    
      Order anonymousOrder = new OrderController().FetchOrder(anonymousCartId);
      Order registeredOrder = new OrderController().FetchOrder(registeredCartId);

      //first see if there is an order for the now-recognized user
      if (string.IsNullOrEmpty(registeredOrder.OrderNumber) && !string.IsNullOrEmpty(anonymousOrder.OrderNumber)) {
        //if not, just update the old order with the new user's username
        anonymousOrder.UserName = registeredCartId;
        anonymousOrder.Save(registeredCartId);
      }
      else {
        //the logged-in user has an existing basket. 
        //if there is no basket from their anon session, 
        //we don't need to do anything
        if (!string.IsNullOrEmpty(anonymousOrder.OrderNumber)) {

          //in this case, there is an order (cart) from their anon session
          //and an order that they had open from their last session
          //need to marry the items.

          //this part is up to your business needs - some merchants
          //will want to replace the cart contents
          //others will want to synch them. We're going to assume that
          //this scenario will synch the existing items.

          //############### Synch the Cart Items
          if (registeredOrder.OrderItemCollection.Count > 0 && anonymousOrder.OrderItemCollection.Count > 0) {
            //there are items in both carts, move the old to the new
            //when synching, find matching items in both carts
            //update the quantities of the matching items in the logged-in cart
            //removing them from the anon cart

            //a switch to tell us if we need to update the from orders


            //1.) Find items that are the same between the two carts and add the anon quantity to the registered user quantity
            //2.) Mark found items as items to be removed from the anon cart.
            ArrayList toBeRemoved = new ArrayList(anonymousOrder.OrderItemCollection.Count);
            for (int i = 0; i < anonymousOrder.OrderItemCollection.Count; i++) {
              OrderItem foundItem = (OrderItem)registeredOrder.OrderItemCollection.Find(delegate(OrderItem orderItemToFind) {
                return orderItemToFind.Sku == anonymousOrder.OrderItemCollection[i].Sku;
              });
              if (foundItem != null) {
                foundItem.Quantity += anonymousOrder.OrderItemCollection[i].Quantity;
                toBeRemoved.Add(i);
              }
            }
            //3.) Now remove any foundItems from the anon cart, but trim it up first
            toBeRemoved.TrimToSize();
            for (int i = 0; i < toBeRemoved.Count; i++) {
              anonymousOrder.OrderItemCollection.RemoveAt((int)toBeRemoved[i]);
            }

            //4.) Move over to the registered user cart any remaining items in the anon cart.
            foreach (OrderItem anonItem in anonymousOrder.OrderItemCollection) {
              //reset the orderID
              anonItem.OrderId = registeredOrder.OrderId;
              registeredOrder.OrderItemCollection.Add(anonItem);
            }

            //5.) Finally, save it down to the DB
            // (Since we know toOrder.Items.Count > 0 && fromOrder.Items.Count > 0, we know a Save needs to occur)
            registeredOrder.OrderItemCollection.SaveAll(registeredCartId);
            registeredOrder.Save(registeredCartId);
          }
          else if (registeredOrder.OrderItemCollection.Count == 0) {
            //items exist only in the anon cart
            //move the anon items to the new cart
            //then save the order and the order items.
            registeredOrder.IsNew = true;
            registeredOrder.OrderStatusId = anonymousOrder.OrderStatusId;
            registeredOrder.OrderGuid = anonymousOrder.OrderGuid;
            registeredOrder.UserName = registeredCartId;
            registeredOrder.OrderNumber = anonymousOrder.OrderNumber;
            registeredOrder.Save(registeredCartId);
            foreach (OrderItem item in anonymousOrder.OrderItemCollection) {
              //reset the orderID on each item
              item.OrderId = registeredOrder.OrderId;
              registeredOrder.OrderItemCollection.Add(item);
            }
            registeredOrder.OrderItemCollection.SaveAll(registeredCartId);
            registeredOrder.Save(registeredCartId);

          }
          else if (anonymousOrder.OrderItemCollection.Count == 0) {
            //no items in the old cart, do nothing
          }

          //finally, drop the anon order from the DB, we don't want to 
          //keep it
          OrderItem.Delete(OrderItem.Columns.OrderId, anonymousOrder.OrderId);
          Order.Delete(anonymousOrder.OrderId);
        }
      }
  
    }
    
    #region Private

    /// <summary>
    /// Resets the shipping and tax and discount.
    /// </summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="userName">Name of the user.</param>
    private static void ResetShippingAndTaxAndDiscount(int orderId, string userName) {
      //Clear Shipping and Tax if in there
      Order order = new Order(orderId);
      if(order.OrderId > 0) {
        order.TaxAmount = 0;
        order.ShippingAmount = 0;
        order.ShippingMethod = string.Empty;
        order.DiscountAmount = 0;
        order.Save(userName);
      }
    }
    
        /// <summary>
    /// Provisions the order.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <returns></returns>
    private int ProvisionOrder(string userName) {
      int orderId = GetOrderId(userName);
      if(orderId == 0) {
        Order order = new Order();
        order.OrderGuid = Guid.NewGuid().ToString();
        order.OrderNumber = CoreUtility.GenerateRandomString(ORDER_NUMBER_LENGTH);
        order.OrderStatusId = (int)OrderStatus.NotProcessed;
        order.UserName = userName;
        order.IPAddress = HttpContext.Current.Request.UserHostAddress;
        order.Save(userName);
        orderId = order.OrderId;
      }
      return orderId;
    }
    
    #endregion

    #endregion
    
    #endregion
    
  }
}
